﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Diagnostics;

namespace IPCByMessages
{
    public abstract class AsyncMessagesChannelBase : MessagesChannelBase
    {
        private object locker = new object();

        /// <summary>
        /// Close connection and release all resources.
        /// Channel becomes not usable after call.
        /// </summary>
        public override void Close()
        {
            Close(false);
        }

        /// <summary>
        /// Send message
        /// </summary>
        /// <param name="data">Data to send</param>
        public override void SendMessage(params byte[] data)
        {
            if (!Active || (otherSide == null) || !otherSide.Connected)
                return;

            var toSend = parser.MakePacket(data);

            try
            {
                otherSide.Send(toSend);
            }
            catch (SocketException)
            {
                Close(true);
            }
        }

        #region Protected methods

        /// <summary>
        /// Begin to wait for message. Receive buffer is passed as State
        /// </summary>
        protected virtual void BeginReceive()
        {
            //lock (locker)
            {
                if ((otherSide == null) || !otherSide.Connected)
                {
                    Close(true);
                    return;
                }

                var error = SocketError.Success;

                var buffer = new byte[otherSide.Available];

                try
                {
                    otherSide.BeginReceive(buffer,
                        0,
                        buffer.Length,
                        SocketFlags.None,
                        out error,
                        ReceiveCallback,
                        buffer);
                }
                catch (SocketException)
                {
                    Close(true);
                }
            }
        }

        /// <summary>
        /// Receive packet and handle it
        /// </summary>
        /// <param name="res">Async call result</param>
        protected virtual void ReceiveCallback(IAsyncResult res)
        {
            if ((otherSide == null) || !otherSide.Connected)
            {
                Close(true);
                return;
            }

            var error = SocketError.Success;

            var justReceived = res.AsyncState as byte[];

            try
            {
                var byteNum = otherSide.EndReceive(res, out error);
            }
            catch (SocketException)
            {
                error = SocketError.ConnectionAborted;
            }
            if (error != SocketError.Success)
            {
                Close(true);
                return;
            }

            //int oldLen = totalBuffer.Length;
            //Array.Resize(ref totalBuffer, oldLen + justReceived.Length);
            //for (int i = 0; i < justReceived.Length; i++)
            //    totalBuffer[oldLen + i] = justReceived[i];

            //IPCUtils.DebugMessage("Received");

            //var messages = TryExtractMessages(ref totalBuffer);
            //foreach (var message in messages)
            //{
            //    IPCUtils.DebugMessage("Message");
            //    ThrowMessageReceived(message);
            //}

            foreach (var newByte in justReceived)
            {
                var msg = parser.HandleNewByte(newByte);
                if (msg != null)
                    ThrowMessageReceived(msg);
            }

            if (otherSide.Connected)
                BeginReceive();
        }

        /// <summary>
        /// Close connection and release all resources.
        /// Channel becomes not usable after call.
        /// </summary>
        /// <param name="throwDisconnected">
        /// Specifies if Disconnected event must be thrown.
        /// </param>
        protected void Close(bool throwDisconnected)
        {
            if (Closed)
                return;

            Active = false;
            Closed = true;

            if ((connection != null) && connection.Connected)
            {
                //connection.Disconnect(false);
                connection.Close();
                connection = null;
            }

            if (otherSide != null)
            {
                otherSide.Close();
                otherSide = null;
            }

            if (throwDisconnected)
                ThrowDisconnected();
        }

        #endregion
    }
}
